#!/usr/bin/env bash
# set -x

# 不满足条件时全部拦截
[[ ${BASH_SOURCE[0]} == $SHEL ]] && echo "错误: 不接受 source 内建执行" && return
[ -z $(command -v wget) ] && exec echo "错误: 找不到命令 wget"
[ -z $1 ] && exec echo "用法: ${BASH_SOURCE[0]} [url1] [url2]... [url3]..."

# 默认值，这将允许从环境变量或局部变量更换
: ${PWGET_DLLOG_DIR=$HOME/.pwget/log}
: ${PWGET_PIDLOG_DIR=$HOME/.pwget/plog}
: ${PWGET_MAX_JOBS=10}  # 默认最大并发数
: ${PWGET_MAX_BATCH=$PWGET_MAX_JOBS}  # 默认最大并发数
: ${PWGET_MODE=batch}  # 任务分配模式

declare -g processed=0
declare -g total_urls=0
declare -g DL_URLS=()

if ! [[ "$PWGET_MODE" =~ ^(all|batch|cont)$ ]]; then
  exec echo "错误: PWGET_MODE 必须是 all|batch|cont"
fi

# 检查 PWGET_MAX_BATCH 是否为有效值
if [[ -n "$PWGET_MAX_BATCH" && ! "$PWGET_MAX_BATCH" =~ ^[0-9]+$ ]]; then
  exec echo "错误: PWGET_MAX_BATCH 必须是一个正整数"
fi

pwget_stop=false

# 批量递归创建目录
for i in $PWGET_DLLOG_DIR $PWGET_PIDLOG_DIR;do
  [ -d $i ] || mkdir -p $i
done

# 创建并行下载任务队列
DL_QE=()
DL_PID=()
DL_URLS=()  # 存储所有URL
DL_NAME=()  # 存储所有URL的文件名

# 创建异常序列
DL_ERR=()

# 多任务进度显示函数
# 参数: 任务数量，各个任务的当前进度和总进度
multi_task_progress(){
  local task_count=$1
  shift
  
  # 隐藏光标
  printf "\e[?25l"
  
  # 显示每个任务的进度
  for ((i=1; i<=task_count; i++)); do
    local current_progress_var="\$1"
    local total_progress_var="\$2"
    shift 2
    
    local current_progress=${!current_progress_var}
    local total_progress=${!total_progress_var}
    
    # 计算百分比
    if [ $total_progress -eq 0 ]; then
      local percentage=0
    else
      local percentage=$((current_progress * 100 / total_progress))
    fi
    
    # 显示进度条
    pwget_progress $percentage 20
    echo " $i: ${percentage}%"
  done
  
  # 移动光标到开始位置
  printf "\e[${task_count}A"
}

# 主要的多任务下载实现
pwget_main(){
  # 保存所有URL
  DL_URLS=($(awk '!seen[$0]++' <<<$@))
  total_urls=${#DL_URLS[@]}
  processed=0

  echo "需要下载${total_urls}个文件"
  echo "按 Ctrl + C 以终止所有任务..."
  
  if [[ "$PWGET_MODE" == "cont" ]]; then
    echo "开启连续下载模式，将同时进行 $PWGET_MAX_JOBS 个任务，每有任务完成将接续新任务"
    # 连续模式：初始启动最大并发数的任务
    local initial_jobs=$((PWGET_MAX_JOBS < total_urls ? PWGET_MAX_JOBS : total_urls))
    for ((i=0; i<initial_jobs; i++)); do
      pwget_task "${DL_URLS[i]}"
      processed=$((i+1))
    done
    # 调用pwget_info跟踪任务，该函数会在任务完成时添加新任务
    pwget_info
  else
    # 批处理模式
    if [[ -z "$PWGET_MAX_BATCH" || "$PWGET_MAX_BATCH" -eq 0 ]]; then
      PWGET_MAX_BATCH=$total_urls
    fi

    [[ "$PWGET_MAX_BATCH" -lt "${#DL_URLS[@]}" ]] && 
    echo "开启分批下载，共$(((total_urls + PWGET_MAX_BATCH - 1) / PWGET_MAX_BATCH))批次"
    
    local batch_count=0
    while ((processed < total_urls)); do
      # 清空上一批次的任务数组
      DL_QE=()
      DL_PID=()
      
      # 计算当前批次需要处理的URL数量
      local batch_end=$((processed + PWGET_MAX_BATCH))
      if ((batch_end > total_urls)); then
        batch_end=$total_urls
      fi
      
      # 处理当前批次
      for ((i=processed; i<batch_end; i++)); do
        pwget_task "${DL_URLS[i]}"
      done
      
      # 移除PID和wget进程提示
      # echo "将启用${#DL_PID[@]}个wget进程"
      # echo "PID: ${DL_PID[@]}"
      
      # 执行当前批次的下载跟踪
      pwget_info
      
      # 更新已处理的URL数量
      processed=$batch_end
      batch_count=$((batch_count + 1))
    done
  fi
}

# 生成下载任务
pwget_task(){
  local url=$1
  local TASK_ID="$(date +%Y%m%d%H%M%S%N)"
  local TASK_NAME="$(basename "$url")"
  LANG=en_US.UTF-8 wget --show-progress $url -bo "$PWGET_DLLOG_DIR/pwget_${TASK_ID}.log" &>"$PWGET_PIDLOG_DIR/pwget_${TASK_ID}.log"
  local PID="$(grep -oE '[0-9]+' "$PWGET_PIDLOG_DIR/pwget_${TASK_ID}.log")"
  DL_PID+=("$PID")
  DL_QE+=("$TASK_ID")
  DL_NAME+=("$TASK_NAME")
}

# 多进程等待
# 指定等待多个进程，在所有被等待进程结束后返回 true
pwget_sleep(){
  local argv=($(awk '!seen[$0]++' <<<"$@"))
  local wait=0
  local args=${#argv[@]}
  for i in ${argv[@]};do
    # 典型的进程查询方法
    kill -0 $i &>/dev/null || let wait++
  done
  if ((wait == args));then
    return 0
  else
    return 1
  fi
}

# 进程跟踪
# 当某个进程结束后删除该进程所占序列
# 使用 wait 内建命令跟踪退出状态，如为异常退出则另外追加到异常序列
pwget_wait(){
  for i in ${DL_PID[@]};do
    wait $i
    DL_ERR+=($i)
  done
}

# 进度条制作，理想效果为根据终端宽度自适应
# 输入整数进度（%）和需让出的宽度，制作长度匹配的进度条；未指定让出宽度则占满终端宽度。
pwget_progress(){
  # 进度参数
  if [[ -z $1 || ! $1 =~ ^[0-9]{1,3}$ ]];then
    local PGR=0
  else
    local PGR=$1
  fi
  # 宽度参数
  # eval 直接构建参数执行，简化结构
  eval "local PGR_W=${2:-0}"

  # 开始制作进度条
  # 获取宽度
  local TPUT_W=$(tput cols)
  local PR_COLS=$(((PGR * (TPUT_W - PGR_W - 2)) / 100))
  local PR_COLS1=$(((100 * (TPUT_W - PGR_W - 2)) / 100))
  local PR_COLS2=$((PR_COLS1 - PR_COLS))
  printf [
  printf '%*s' $PR_COLS | tr ' ' '#'
  printf '%*s' $PR_COLS2
  printf ]  
}

# 清除日志缓存
clear_cache(){
    rm $PWGET_DLLOG_DIR/pwget_"$1".log $PWGET_PIDLOG_DIR/pwget_"$1".log &>/dev/null
    unset DL_QE[$2] DL_PID[$2] DL_NAME[$2]
    DL_QE=("${DL_QE[@]}")
    DL_PID=("${DL_PID[@]}")
    DL_NAME=("${DL_NAME[@]}")
    return 0
}

# 作为信号陷阱使用
pwget_stop(){
  pwget_stop=true
}

# 多任务信息显示
pwget_info(){
  # 仅在第一批次设置信号陷阱
  if ((batch_count == 0)); then
    trap pwget_stop EXIT
    trap pwget_stop SIGINT
  fi

  stty -echo
  printf "\e[?25l"

  # 跟踪下载任务
  while true;do
    
    # 如果所有队列完成，将清除可能残余的缓存数据
    pwget_sleep "${DL_PID[@]}" && {
      rm $PWGET_DLLOG_DIR/pwget_*.log $PWGET_PIDLOG_DIR/pwget_*.log &>/dev/null
      break
    }

    # 制作进度信息
    local ECHO_LINE=0
    while [ $ECHO_LINE -lt ${#DL_QE[@]} ]; do
      local pwget_item="${DL_QE[ECHO_LINE]}"
      local LOG=$(tail -n2 $PWGET_DLLOG_DIR/pwget_"${pwget_item}".log 2>/dev/null | head -1)
      
      # 获取下载信息元素
      local PWGET_INFO="$(sed 's/K100%/K 100%/' <<<${LOG:0:25})"
      # echo -e "\e[K$LOG"
      
      # 下载进度，%
      local PWGET_PR=$(awk '{print $2}' <<< $PWGET_INFO | grep -oE '^[0-9]+%$' | tr -d '%')
      [ -z $PWGET_PR ] && local PWGET_PR=--
      
      # 预计剩余时间
      local PGWET_ETA=$(awk '{print $4}' <<< $PWGET_INFO | grep -oE '^[0-9dhms]+$')
      [ -z $PGWET_ETA ] && local PGWET_ETA=--s
      
      # 已下载大小
      local PGWET_DLS=$(awk '{print $1}' <<< $PWGET_INFO | grep -oE '^[0-9K]+$')
      [ -z $PGWET_DLS ] && local PGWET_DLS=--K
      
      # 速度 s
      local PWGET_DLSP=$(awk '{print $3}' <<< $PWGET_INFO | grep -oE '^[0-9GMKB.]+$')
      [ -z $PWGET_DLSP ] && local PWGET_DLSP=--

      # 任务名称
      local PWGET_NAME="${DL_NAME[ECHO_LINE]}"
      ((`wc -L <<< $PWGET_NAME` > 8)) && PWGET_NAME=${PWGET_NAME:0:12}...
      
      # 固定宽度格式化信息
      local DL_INFO=$(printf "| %8s %8sB %8sB/s %3s%% [ETA:%4s]" "$PWGET_NAME" "$PGWET_DLS" "$PWGET_DLSP" "$PWGET_PR" "$PGWET_ETA")
      local INFO_W=$(($(wc -L <<<$DL_INFO) + 1))
      local PWGET_DL_INFO="$(pwget_progress $PWGET_PR $INFO_W) $DL_INFO"
      echo -e "\e[?25l$PWGET_DL_INFO\e[K"
      
      # 满进度时该任务退出队列
      if ((PWGET_PR == 100)) &>/dev/null;then
        clear_cache "$pwget_item" $ECHO_LINE
        printf "\r\e[K\e[1A"
        
        # 如果是连续模式且还有未处理的URL，添加新任务
        if [[ "$PWGET_MODE" == "cont" && $processed -lt $total_urls ]]; then
          pwget_task "${DL_URLS[processed]}"
          processed=$((processed + 1))
        fi
      else
        let ECHO_LINE++
      fi
    done
    if $pwget_stop;then
      stty echo
      for i in ${DL_PID[@]};do
        kill -9 $i
        printf "\r\e[K\e[1A"
      done
    else
      [[ $ECHO_LINE -gt 0 ]] && printf "\r\e[K\e[%sA" "$ECHO_LINE"
    fi
  done
  printf "\r\e[K\e[?25h\e[0m"
  stty echo
  $pwget_stop && exec echo "进程已终止"
}

# 开始下载
pwget_main $@

echo "所有下载任务已完成"